Proxy Headers
When NGINX works as a reverse proxy, it sits between the client and the backend server.
Client ──► NGINX (proxy) ──► Backend
Without proxy headers, the backend only sees:
- NGINX’s IP address
- NGINX’s protocol (http/https)
- NGINX as the direct client
X-Forwarded-* headers preserve original client information.
Why Proxy Headers Are Needed
They allow backend applications to know:
- Real client IP address
- Original host/domain requested
- Original protocol (HTTP or HTTPS)
- Proxy chain (multiple proxies)
Used for:
- Logging
- Security rules
- Authentication
- Redirects
- Geo/IP detection
- Rate limiting
Core X-Forwarded-* Headers
X-Forwarded-For
- Purpose: Identifies the original client IP address.
- Format
X-Forwarded-For: client_ip, proxy1_ip, proxy2_ip - NGINX Directive:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
Example Flow
Client IP: 203.0.113.10
X-Forwarded-For: 203.0.113.10
With multiple proxies:
X-Forwarded-For: 203.0.113.10, 192.168.1.1
Backend Use
- Detect real client IP
- Prevent abuse
- Accurate logging
X-Real-IP
- Purpose: Provides a single client IP address (simpler than
X-Forwarded-For). - NGINX Directive:
proxy_set_header X-Real-IP $remote_addr;
Example
X-Real-IP: 203.0.113.10
Difference from X-Forwarded-For
| Header | Use |
|---|---|
X-Real-IP | Simple, one IP |
X-Forwarded-For | Full proxy chain |
X-Forwarded-Proto
- Purpose: Tells the backend the original protocol used by the client.
- Values:
http,https - NGINX Directive:
proxy_set_header X-Forwarded-Proto $scheme;
Example
Client accesses:
https://example.com
Backend receives:
X-Forwarded-Proto: https
Why It Matters
- Correct redirects
- OAuth callbacks
- Secure cookies
- HTTPS enforcement
X-Forwarded-Host
- Purpose: Contains the original Host header requested by the client.
- NGINX Directive:
proxy_set_header X-Forwarded-Host $host;
Example
X-Forwarded-Host: example.com
X-Forwarded-Port
- Purpose: Indicates the original port used by the client.
- NGINX Directive:
proxy_set_header X-Forwarded-Port $server_port;
Example
X-Forwarded-Port: 443
The Host Header (Important!)
Although not X-Forwarded-\*, it is critical.
proxy_set_header Host $host;
- Preserves original domain name
- Required for:
- Virtual hosting
- TLS SNI
- Correct routing
Recommended Standard Proxy Header Set
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
}
This is considered best practice.
End-to-End Request Example
Client Request
GET /login HTTP/1.1
Host: example.com
Headers Sent to Backend
Host: example.com
X-Real-IP: 203.0.113.10
X-Forwarded-For: 203.0.113.10
X-Forwarded-Proto: https
X-Forwarded-Host: example.com
X-Forwarded-Port: 443
Application-Level Usage
Example: Backend Redirect Logic
if request.headers["X-Forwarded-Proto"] == "https":
secure = True
Example: Logging Real IP
$remote_addr → NGINX IP
X-Forwarded-For → Client IP
Security Considerations
These headers can be spoofed by clients.
Best Practices
- Only trust headers from known proxies
- Use NGINX
real_ipmodule to sanitize - Strip incoming
X-Forwarded-\*headers
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
(Overwrites client-sent value)
X-Forwarded-\* vs Forwarded (RFC 7239)
Modern Alternative
Forwarded: for=203.0.113.10;proto=https;host=example.com
NGINX supports it, but:
X-Forwarded-\*is still more widely used- Most frameworks expect
X-Forwarded-\*
Common Mistakes
- Forgetting
X-Forwarded-Proto(causes redirect loops) - Trusting client-provided headers
- Missing
Hostheader - Using
$remote_addrincorrectly
Real-World SSL Termination Example
server {
listen 443 ssl;
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Backend runs HTTP but behaves as HTTPS-aware.